package mylab.starters.mybatis.interceptor.impl.tenant.impl;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.text.CharSequenceUtil;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.google.common.base.Joiner;
import lombok.extern.slf4j.Slf4j;
import mylab.common.context.TenantContext;
import mylab.starters.mybatis.interceptor.OrderlyInterceptor;
import mylab.starters.mybatis.config.tenant.TenantMode;
import mylab.starters.mybatis.config.tenant.TenantProperties;
import mylab.starters.mybatis.interceptor.impl.tenant.TenantInterceptor;
import mylab.starters.mybatis.utils.DruidSqlParserUtil;
import mylab.starters.mybatis.utils.JSqlParserUtil;
import net.sf.jsqlparser.JSQLParserException;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.sql.Connection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * SCHEMA 模式插件
 */
@Slf4j
public class TenantSchemaModeInterceptor implements InnerInterceptor, TenantInterceptor {

    TenantProperties.Schema schema;

    public TenantSchemaModeInterceptor(TenantProperties.Schema schema) {
        this.schema = schema;
        Assert.notBlank(schema.getTenantPrefix(), "无效的租户schema前缀");
    }

    @Override
    public int getOrder() {
        return OrderlyInterceptor.TenantSchema;
    }

    @Override
    public String getDescription() {
        return "租户插件-数据库隔离";
    }

    @Override
    public TenantMode getTenantMode() {
        return TenantMode.SCHEMA;
    }

    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // 统一交给 beforePrepare 处理,防止某些sql解析不到，又被beforePrepare重复处理
    }

    @Override
    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
        PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
        MappedStatement ms = mpSh.mappedStatement();
        SqlCommandType sct = ms.getSqlCommandType();
        if (sct == SqlCommandType.INSERT || sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE || sct == SqlCommandType.SELECT) {
            String tenantId = TenantContext.getTenantId();
            if (CharSequenceUtil.isEmpty(tenantId)) {
                //当前会话不是租户登录，则直接返回
                return;
            }
            PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
            String oldSql = mpBs.sql();
            //得到sql语句中涉及的表名
            List<String> tables = JSqlParserUtil.getTables(oldSql);
            //取交集: tables 与 ignoreTables
            List<String> intersection = tables.stream()
                    .filter(item -> schema.getIgnoreTables().contains(item))
                    .collect(Collectors.toList());
            if (!intersection.isEmpty()) {
                log.error("sql语句中包含不需要切换schema的表:{}", Joiner.on(",").join(intersection));
                return;
            }

            log.debug("未替换前的sql: \n{}", oldSql);
            String newSql = this.changeTable(tenantId, oldSql);
            log.debug("替换后的sql: \n{}", newSql);
            mpBs.sql(newSql);
        } else {
            log.warn("未替换schema, SqlCommandType:{}", sct);
        }
    }

    protected String changeTable(String tenantId, String sql) {
        String schemaName = CharSequenceUtil.format("{}_{}", schema.getTenantPrefix(), tenantId);
        log.debug("替换后的schema: {}", schemaName);

        try {
            //        return DruidSqlParserUtil.replaceSchema(schemaName, sql);
            return JSqlParserUtil.replaceSchema(schemaName, sql);
        } catch (JSQLParserException e) {
            throw new RuntimeException(e);
        }
    }

}
